package com.letoo.dragon.interceptor;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import com.letoo.dragon.common.domain.Result;
import com.letoo.dragon.common.domain.ResultCode;
import com.letoo.dragon.common.utils.AESUtils;
import com.letoo.dragon.common.utils.DateUtil;
import com.letoo.dragon.common.utils.RegexUtils;
import com.letoo.sso.client.Auth;

public class AuthInterceptor implements HandlerInterceptor {
	/**
	 * 日志
	 */
	protected final Logger LOG = LoggerFactory.getLogger(getClass());

	/**
	 * 默认为线上环境
	 */
	private String AUTH_URL = StringUtils.EMPTY;
	/**
	 * 需要排除的URL。一部分外部接口可以允许不进过token校验
	 */
	private String[] excludedUrlArray = new String[] {};

	private boolean isValidate = true;

	public AuthInterceptor(String authUrl, String excludedUrl, boolean isValidate) {
		this.AUTH_URL = authUrl;
		this.isValidate = isValidate;
		if (StringUtils.isNotEmpty(excludedUrl)) {
			excludedUrlArray = excludedUrl.split(",");
		}
	}

	@Override
	public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
			throws Exception {
		LOG.debug(">>>AuthInterceptor>>>>>>>在整个请求结束之后被调用，也就是在DispatcherServlet 渲染了对应的视图之后执行（主要是用于进行资源清理工作）");
	}

	@Override
	public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
			throws Exception {
		LOG.debug(">>>AuthInterceptor>>>>>>>请求处理之后进行调用，但是在视图被渲染之前（Controller方法调用之后）");
	}

	@Override
	public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception {
		LOG.debug(">>>AuthInterceptor>>>>>>>在请求处理之前进行调用（Controller方法调用之前）");
		// 只有返回true才会继续向下执行，返回false取消当前请求
		return doFilter(arg0, arg1);
	}

	private boolean validateParams(long userId, String token, String uri, String sign) {
		if (userId == 0 || StringUtils.isBlank(token) || StringUtils.isBlank(uri) || StringUtils.isBlank(sign)) {
			LOG.error("the params is illegal.the uri is:" + uri + ";the userId is:" + userId + ";the token is:" + token
					+ ";the sign is:" + sign);
			return false;
		}
		return true;
	}

	/**
	 * 判断x参数验证。判断客户端时间戳和服务器时间戳是否超过5分钟.密钥采用固定密钥。放在每个系统中减少了内部请求量
	 * 新增了鉴权参数x。x参数也是放在header中进行传输。作用：为了解决这些接口的访问控制因为失去了鉴权后，就要考虑一下，不能让一些用户疯狂访问。
	 * 如何减少疯狂访问：认证是我们APP发出来的请求即可完成。
	 * 
	 * 解决方案：新增校验参数：x=AES对称加密（当前时间戳），密钥采用baseconfig中的base_config_sercret_key,就是：45ryu230a@n2x302l。固定密钥，
	 * 这样一般的手机和服务器的分钟应该都是一致的，很难偏差到小时分钟级别，校验的时候就只校验5分钟偏差。也就是说用户在这个5分钟内是可以模拟攻击的，以外不行。这样可以减少那种损人不利己的攻击带来的影响。这个逻辑类似：RSA
	 * SecurID 动态令牌逻辑。
	 * 
	 * @param x
	 * @return
	 */
	private boolean validateX(String x) {
		// 校验x参数。时间戳
		if (StringUtils.isBlank(x)) {
			LOG.error("the params is illegal.the x is:" + x);
			return false;
		}
		// 先校验x参数是否正确。真实x参数格式：2016-03-29 20:10:56
		String realX = AESUtils.decrypt(x, "45ryu230a@n2x302");
		// 计算差异时间，单位秒
		long nowTime = System.currentTimeMillis();
		long realXTimeMillis = Long.valueOf(realX);
		long diffTime = nowTime - realXTimeMillis;
		if (diffTime > 300000) {
			// 大于5分钟后，就鉴权失败
			LOG.error("the x is timeout,the x is:" + DateUtil.getDateTime(realXTimeMillis) + "the server now time is:"
					+ DateUtil.getDateTime(nowTime));
			return false;
		}
		return true;
	}

	/**
	 * 过滤URL进行鉴权操作
	 */
	public boolean doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
		if (isValidate == false) {
			// 请求URL被过滤不做鉴权操作。
			return true;
		}
		boolean isExcludedPage = false;
		HttpServletRequest httpServletRequest = (HttpServletRequest) request;
		for (String url : excludedUrlArray) {
			// 判断是否在过滤url之外
			if (RegexUtils.match(url, httpServletRequest.getServletPath())) {
				isExcludedPage = true;
				break;
			}
		}
		// x参数就是认证客户端的标志。
		String x = httpServletRequest.getHeader("x");
		if (!validateX(x)) {
			// 校验x参数，true表示通过则继续下面校验。false表示x参数失败后面都不需要进行校验了。直接全部鉴权失败。包含不鉴权的url。
			Result result = null;
			try {
				result = new Result(ResultCode.COMMON_NO_PERMISSION, false);
				response.setCharacterEncoding("UTF-8");
				response.setContentType("application/json;charset=UTF-8");
				response.getWriter().println(com.alibaba.fastjson.JSON.toJSONString(result));
				return false;
			} catch (Exception e) {
				LOG.error("Response write exception", e);
			} finally {
				result = null;
			}
		}
		if (isExcludedPage) {
			// 请求URL被过滤不做鉴权操作。
			return true;
		} else {
			// 外部接口进行SSO token的鉴权验证操作。
			long userId = Long.valueOf(httpServletRequest.getHeader("userId"));
			String token = httpServletRequest.getHeader("token");
			String uri = httpServletRequest.getRequestURI();
			String sign = httpServletRequest.getHeader("sign");

			// 对输入的参数进行校验。有问题直接返回不用远程校验
			if (!validateParams(userId, token, uri, sign)) {
				Result result = null;
				try {
					result = new Result(ResultCode.COMMON_NO_PERMISSION, false);
					response.setCharacterEncoding("UTF-8");
					response.setContentType("application/json;charset=UTF-8");
					response.getWriter().println(com.alibaba.fastjson.JSON.toJSONString(result));
					return false;
				} catch (Exception e) {
					LOG.error("Response write exception", e);
				} finally {
					result = null;
				}
			}
			boolean res = Auth.validateToken(AUTH_URL, userId, token, uri, sign);
			if (res) {
				return true;
			} else {
				// 鉴权失败，就直接返回错误信息给前端
				Result result = new Result(ResultCode.COMMON_NO_PERMISSION, false);
				result.setDescription(ResultCode.COMMON_NO_PERMISSION.getDescription());
				LOG.error("the request is illegal.the uri is:" + uri + ";the userId is:" + userId + ";the token is:"
						+ token + ";the sign is:" + sign);
				try {
					response.setCharacterEncoding("UTF-8");
					response.setContentType("application/json;charset=UTF-8");
					response.getWriter().println(com.alibaba.fastjson.JSON.toJSONString(result));
				} catch (Exception e) {
					LOG.error("Response write exception", e);
				} finally {
					result = null;
				}
				return false;
			}
		}
	}

}
